package com.chatplus.application.common.crypto;

import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.HmacAlgorithm;
import cn.hutool.crypto.symmetric.PBKDF2;

import java.nio.ByteBuffer;
import java.security.MessageDigest;

/**
 * C# PBKDF2密码哈希算法实现，C#算法源码：https://github.com/dotnet/aspnetcore/blob/main/src/Identity/Extensions.Core/src/PasswordHasher.cs
 * @author Angus
 */
public final class PBKDF2PasswordHasher {
    private static final byte MAGIC_HEAD = 0x01;
    private static final int PRF = 0x00000001;
    private static final int ITER_COUNT = 10000;
    private static final int SALT_LENGTH = 16;
    private static final int SUB_KEY_LENGTH = 32;
    private static final String ALGORITHM = HmacAlgorithm.HmacSHA256.getValue();

    static byte[] HEADER_BYTES = {1, 0, 0, 0, 1, 0, 0, 39, 16, 0, 0, 0, 16};

    /**
     * PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations.
     * Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey }
     * (All UInt32s are stored big-endian.)
     */
    private final static PBKDF2 PBKDF_2 = new PBKDF2("PBKDF2With" + ALGORITHM, SUB_KEY_LENGTH * 8, ITER_COUNT);

    /**
     * PBKDF2算法实现密码哈希
     * @param password 原始密码
     * @return 密码哈希后的base64串
     */
    public static String hashPassword(String password) {
        // magic(1) + prf(4) + iterCount(4) + salt(16) + subkey(32)
        ByteBuffer buffer = ByteBuffer.allocate(61);
        buffer.put(MAGIC_HEAD);
        buffer.putInt(PRF);
        buffer.putInt(ITER_COUNT);
        buffer.putInt(SALT_LENGTH);

        byte[] salt = SecureUtil.generateKey(ALGORITHM, SALT_LENGTH * 8).getEncoded();
        buffer.put(salt, 0 , SALT_LENGTH);

        byte[] subKey = PBKDF_2.encrypt(password.toCharArray(), salt);
        buffer.put(subKey, 0 , SUB_KEY_LENGTH);

        return Base64.encode(buffer.array());
    }

    /**
     * 验证哈希的密码base64串与提供的明文密码是否一致
     * @param hashedPassword 希的密码base64串
     * @param providedPassword 明文密码
     * @return 一致返回true，反之返回false
     */
    public static boolean verifyHashedPassword(String hashedPassword, String providedPassword) {
        byte[] bytes = Base64.decode(hashedPassword);
        ByteBuffer buffer = ByteBuffer.wrap(bytes);

        buffer.position(9); // discard [magic(1) + prf(4) + iterCount(4)] position
        int saltLen = buffer.getInt(); //9-12

        byte[] salt = new byte[saltLen];
        buffer.get(salt, 0, salt.length);

        byte[] subKey = new byte[buffer.remaining()];
        buffer.get(subKey, 0, subKey.length);

        char[] password = providedPassword.toCharArray();
        byte[] encrypt = PBKDF_2.encrypt(password, salt);

        return MessageDigest.isEqual(subKey, encrypt);
    }

    /**
     * 提供的密码是否是该算法生成的hashedPassword
     * @param encodedPassword 提供的密码
     * @return true or false
     */
    public static boolean isSupport(String encodedPassword) {
        ByteBuffer buffer = ByteBuffer.wrap(Base64.decode(encodedPassword));
        if (buffer.array().length <= 13) {
            return false;
        }
        for (int i = 0; i < HEADER_BYTES.length; i++) {
            if (HEADER_BYTES[i] != buffer.array()[i]) {
                return false;
            }
        }
        return true;
    }
}
